package com.ut.richpath;

import com.ut.richpath.model.Vector;
import com.ut.richpath.pathparser.PathParser;
import com.ut.richpath.util.LogUtil;
import com.ut.richpath.util.PathUtils;
import ohos.agp.components.Image;
import ohos.agp.components.element.VectorElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Path;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Rect;
import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.List;

public class RichPathDrawable extends VectorElement {

    private Vector vector;
    private Image.ScaleMode scaleMode;
    private int width, height;
    private RefreshCallback refreshCallback;
    private Context context;


    public RichPathDrawable(Context context, int resId, Vector vector, Image.ScaleMode scaleMode, RefreshCallback refreshCallback) {
        super(context, resId);
        this.vector = vector;
        this.context = context;
        this.scaleMode = scaleMode;
        this.refreshCallback = refreshCallback;
        listenToPathsUpdates();
    }

    public void onBoundsChange(Rect bounds){
        if (bounds==null)return;
        if (bounds.getWidth() > 0 && bounds.getHeight() > 0){
            width = bounds.getWidth();
            height = bounds.getHeight();
            mapPaths();
        }
    }

    private void mapPaths(){
        if (vector==null)return;
        float centerX = width/2f;
        float centerY = height/2f;
        Matrix matrix = new Matrix();
        matrix.postTranslate(centerX - vector.getCurrentWidth() / 2, centerY - vector.getCurrentHeight() / 2);
        float widthRatio = width/vector.getCurrentWidth();
        float heightRatio = height/vector.getCurrentHeight();
        LogUtil.i("width: %d, widthRatio: %f, height:%d, heightRatio: %f", width, widthRatio, height, heightRatio);
        if (scaleMode == Image.ScaleMode.STRETCH) {
            matrix.postScale(widthRatio, heightRatio, centerX, centerY);
        }else if (scaleMode == Image.ScaleMode.INSIDE) {
            float ratio = Math.min(widthRatio, heightRatio);
            matrix.postScale(ratio, ratio, centerX, centerY);
        } else {
            float ratio = width < height ? widthRatio : heightRatio;
            matrix.postScale(ratio, ratio, centerX, centerY);
        }

        float absWidthRatio = width / vector.getViewportWidth();
        float absHeightRatio = height / vector.getViewportHeight();
        float absRatio = Math.min(absWidthRatio, absHeightRatio);

        for (RichPath path : vector.getPaths()) {
            path.mapToMatrix(matrix);
            path.scaleStrokeWidth(absRatio);
        }
        vector.setCurrentWidth(width);
        vector.setCurrentHeight(height);
    }

    public List<RichPath> findAllRichPaths(){
        return vector == null ? new ArrayList<>() : vector.getPaths();
    }

    public RichPath findRichPathByName(String name){
        if (vector == null || TextTool.isNullOrEmpty(name)) return null;
        for (RichPath path : vector.getPaths()) {
            if (name.equalsIgnoreCase(path.getName())) {
                return path;
            }
        }
        return null;
    }

    /**
     * find the first [RichPath] or null if not found
     * <p>
     * This can be in handy if the vector consists of 1 path only
     *
     * @return the [RichPath] object found or null
     */
    public RichPath findFirstRichPath(){
        return findRichPathByIndex(0);
    }

    /**
     * find [RichPath] by its index or null if not found
     * <p>
     * Note that the provided index must be the flattened index of the path
     * <p>
     * example:
     * <pre>
     * {@code <vector>
     *     <path> // index = 0
     *     <path> // index = 1
     *     <group>
     *          <path> // index = 2
     *          <group>
     *              <path> // index = 3
     *          </group>
     *      </group>
     *      <path> // index = 4
     *   </vector>}
     * </pre>
     *
     * @param index the flattened index of the path
     * @return the [RichPath] object found or null
     */
    public RichPath findRichPathByIndex(int index){
        if (vector == null || index < 0 || index >= vector.getPaths().size()) return null;
        return vector.getPaths().get(index);
    }

    private void listenToPathsUpdates() {
        if (vector == null)return;
        for (RichPath path : vector.getPaths()) {
            path.setOnRichPathUpdatedListener(()-> {
                LogUtil.i("invalidate self");
                invalidateSelf();
            });
        }
    }

    public void addPath(String pathData){
        addPath(PathParser.createPathFromPathData(pathData));
    }

    public void addPath(Path path) {
        if (path instanceof RichPath) {
            addPath(path);
        } else {
            addPath(new RichPath(context.getUITaskDispatcher(), path));
        }
    }

    private void addPath(RichPath path) {
        if (vector==null)return;
        vector.getPaths().add(path);
        path.setOnRichPathUpdatedListener(()-> {
            invalidateSelf();
        });
        invalidateSelf();
    }

    public RichPath getTouchedPath(TouchEvent event){
        if (vector==null||event==null)return null;
        if (event.getAction() == TouchEvent.PRIMARY_POINT_UP){
            for (int i=vector.getPaths().size()-1; i>=0; i--) {
                RichPath richPath = vector.getPaths().get(i);
                if (PathUtils.isTouched(richPath, event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY())) {
                    return richPath;
                }
            }
        }
        return null;
    }

    @Override
    public void drawToCanvas(Canvas canvas){
        if (vector == null || vector.getPaths().size() < 0) return;
        LogUtil.i("start draw to canvas");
        for (RichPath path : vector.getPaths()) {
//            LogUtil.i("path: %s", new Gson().toJson(path));
            path.draw(canvas);
//            if (path==vector.getPaths().get(0))break;
        }
//        LogUtil.i("end draw to canvas");
    }

    public void invalidateSelf(){
        if (refreshCallback!=null)refreshCallback.refresh();
    }

    interface RefreshCallback {
        void refresh();
    }
}
